package com.tdd.my;

import com.tdd.jaarta.IllegalComponentException;
import jakarta.inject.Inject;
import jakarta.inject.Provider;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;

import static java.util.Arrays.stream;

/**
 * xx
 *
 * @author Fanhaibo 2022-12-04 10:20:23
 */
public class Context {
    HashMap<Class<?>, Provider<?>> providerMap = new HashMap<>();

    public <T> void bind(Class<T> type, T ins) {
        providerMap.put(type, () -> ins);
    }

    public <T> T get(Class<T> type) {
        return (T) providerMap.get(type).get();
    }

    public <T> void bind(Class<T> type, Class<? extends T> subType) {
        Constructor<? extends T> ctr = getDeclaredConstructorAndCheckInjection(subType);
        providerMap.put(type, new MyProvider<>(subType, ctr));
    }

    private static <T> Constructor<? extends T> getDeclaredConstructorAndCheckInjection(Class<? extends T> subType) {
        List<Constructor<?>> constructorStream = stream(subType.getDeclaredConstructors()).filter(constructor -> constructor.isAnnotationPresent(Inject.class)).toList();
        if (constructorStream.size() > 1) throw new IllegalComponentException();
        return (Constructor<T>) constructorStream.stream().findFirst().orElseGet(() -> {
            try {
                return subType.getDeclaredConstructor();
            } catch (NoSuchMethodException e) {
                throw new IllegalComponentException(e);
            }
        });

    }

    //} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
//private static <T> Constructor<? extends T> getDeclaredConstructorAndCheckInjection(Class<? extends T> subType) {
//    List<Constructor<?>> constructorStream = stream(subType.getDeclaredConstructors()).filter(constructor -> constructor.isAnnotationPresent(Inject.class)).toList();
//    if (constructorStream.size() > 1) throw new IllegalComponentException();
//    return (Constructor<T>) constructorStream
//            .stream().findFirst()
//            .orElseGet(() -> {
//                try {
//                    return subType.getDeclaredConstructor();
//                } catch (NoSuchMethodException e) {
//                    throw new IllegalComponentException(e);
//                }
//            });
//
//
//}
    private class MyProvider<T> implements Provider<T> {
        private final Class<? extends T> subType;

        private boolean cycleFlag = false;
        private Constructor<?> ctr;

        public MyProvider(Class<? extends T> subType, Constructor<? extends T> dtr) {
            this.subType = subType;
            ctr = dtr;

        }

        @Override
        public T get() {
            if (cycleFlag) throw new CycleException();
            cycleFlag = true;
            try {
                ctr.setAccessible(true);
                Object[] parameterTypes = stream(ctr.getParameterTypes())
                        .map(Context.this::get)
                        .toArray(Object[]::new);
                return (T) ctr.newInstance(parameterTypes);
            } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            } finally {
                cycleFlag = false;
            }
        }
    }

//    class Cycle
}
